﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
using DiscoveryLogic.Analytics.DataObjects;
using DiscoveryLogic.Analytics.DataObjects.Visuals;

namespace DiscoveryLogic.Analytics.Diagrams.Physics.Renderers
{
	public class ForceDirected:IGraphRenderer
	{
	    private int _MaxIteration = 5000;
	    private bool _IsStopped;
	    public event OnFinishedGraphRendering GraphRenderFinished;

	    public bool IsStopped
	    {
	        get{ return _IsStopped;}
            set
            {
                if(this._IsStopped !=value)
                {
                    _IsStopped = value;
                    if (this.GraphRenderFinished != null)
                        this.GraphRenderFinished();
                }
            }
	    }
		private double _DampingFactor = 0.9;

		#region Implementation of IGraphRenderer

		/// <summary>
		/// 
		/// </summary>
		/// <param name="vertices"></param>
		/// <param name="edges"></param>
		/// <param name="constraintRect"></param>
		/// <param name="iteration"></param>
		/// <param name="attractForceConst"></param>
		/// <param name="repellingForceConst"></param>
		public void UpdatePositions(List<Vertice> vertices, List<Edge> edges, Rect constraintRect,
			int iteration, double attractForceConst, double repellingForceConst)
		{
            if(iteration > this._MaxIteration)
            {
                this.IsStopped = true;
                return;
            }
			double repulsionRange = Math.Min(constraintRect.Width, constraintRect.Height)/4;
            double maxDistance = 0;
		    double minDistance = double.PositiveInfinity;
            foreach (Edge e in edges)
            {
                if (e.Length > maxDistance)
                    maxDistance = e.Length;
                if(e.Length < minDistance)
                    minDistance = e.Length;
            }

            double scaleFactor = maxDistance * 2 / Math.Max(constraintRect.Width, constraintRect.Height);
		    double verticeWidth = 25;
		    double minEdgeLength = verticeWidth;
            if(minDistance/scaleFactor>minEdgeLength)
            {
                scaleFactor = minDistance/minEdgeLength;
            }

            Dictionary<Guid, Vertice> vs = new Dictionary<Guid, Vertice>();
			foreach(Vertice v in vertices)
			{
				vs.Add(v.ID, v);
			}

		    int numOfMoves = 0;
            
            //// attraction 
            //foreach (Edge e in edges)
            //{
            //    Vertice v1 = vs[e.VerticeID1];
            //    Vertice v2 = vs[e.VerticeID2];
            //    if (v1.CanMove)
            //    {
            //        Vector vector = v2.Position - v1.Position;
            //        double num = Math.Abs(e.Length / scaleFactor - vector.Length);
            //        v1.Force += (Vector)((num * vector) / 750);
            //    }
            //    else if (v2.CanMove)
            //    {
            //        Vector vector = v1.Position - v2.Position;
            //        double num = Math.Abs(e.Length / scaleFactor - vector.Length);
            //        v2.Force += (Vector)((num * vector) / 750);
            //    }
            //}
            //// repulsion 
            //foreach (Vertice v1 in vertices)
            //{
            //    foreach (Vertice v2 in vertices)
            //    {
            //        if (v1.ID != v2.ID)
            //        {
            //            Vector vector = v2.Position - v1.Position;
            //            if (vector.Length < repulsionRange)
            //            {
            //                v2.Force += (Vector)(0.5*(vector) / ((vector.LengthSquared / 11.0) + 3.0));
            //            }
            //        }
            //    }
            //}
            //// move 
            //foreach (Vertice v in vertices)
            //{
            //    if (!v.CanMove)
            //        continue;

            //    Vector vector1 = v.Velocity + ((Vector)(v.Force * 0.1));
            //    Vector vector2 = (Vector)(-0.75 * vector1);
            //    v.Force += vector2;
            //    v.Velocity += v.Force * 0.1;
            //    v.Position += v.Velocity * 0.1;

            //    double rx = 10;
            //    double ry = 10;
            //    if (v.Control != null)
            //    {
            //        rx = v.Control.Width / 2;
            //        ry = v.Control.Height / 2;
            //    }
            //    double x = v.Position.X;
            //    double y = v.Position.Y;
            //    if (x < constraintRect.Left + rx)
            //        x = constraintRect.Left + rx;
            //    if (x > constraintRect.Right - rx)
            //        x = constraintRect.Right - rx;
            //    if (y < constraintRect.Top + ry)
            //        y = constraintRect.Top + ry;
            //    if (y > constraintRect.Bottom - ry)
            //        y = constraintRect.Bottom - ry;
            //    v.Position = new Vector(x, y);

            //    v.Force = new Vector();
            //    if (Math.Abs(v.Velocity.X) > 0 || Math.Abs(v.Velocity.Y) > 0)
            //        numOfMoves++;
            //}

            foreach (Vertice v in vertices)
            {
                Vector newForce = new Vector();
                if (v.CanMove)
                {
                    // attraction 
                    foreach (Edge e in edges)
                    {
                        double distance = (e.Length > 0) ? e.Length / scaleFactor : 10;
                        if (e.VerticeID1 == v.ID)
                        {
                            Vertice v2 = vs[e.VerticeID2];
                            double d = (v.Position - v2.Position).Length;
                            if (d == 0)
                                continue;
                            double springForce = attractForceConst * Math.Log(d / distance);
                            double fx = springForce * (v2.Position.X - v.Position.X) / d;
                            if (fx < -repulsionRange)
                                fx = -repulsionRange;
                            else if (fx > repulsionRange)
                                fx = repulsionRange;
                            double fy = springForce * (v2.Position.Y - v.Position.Y) / d;
                            if (fy < -repulsionRange)
                                fy = -repulsionRange;
                            else if (fy > repulsionRange)
                                fy = repulsionRange;

                            newForce += new Vector(fx, fy);
                        }
                        else if (e.VerticeID2 == v.ID)
                        {
                            Vertice v2 = vs[e.VerticeID1];
                            double d = (v.Position - v2.Position).Length;
                            if (d == 0)
                                continue;
                            double springForce = attractForceConst * Math.Log(d / distance);
                            double fx = springForce * (v2.Position.X - v.Position.X) / d;
                            if (fx < -repulsionRange)
                                fx = -repulsionRange;
                            else if (fx > repulsionRange)
                                fx = repulsionRange;
                            double fy = springForce * (v2.Position.Y - v.Position.Y) / d;
                            if (fy < -repulsionRange)
                                fy = -repulsionRange;
                            else if (fy > repulsionRange)
                                fy = repulsionRange;

                            newForce += new Vector(fx, fy);
                        }
                    }

                    // repelling
                    foreach (Vertice v2 in vertices)
                    {
                        if (v.ID != v2.ID)
                        {
                            double d = (v.Position - v2.Position).Length;
                            if (d > 0 && d < repulsionRange)
                            {
                                double fx = Math.Pow(repellingForceConst / d, 2) * ((v.Position.X - v2.Position.X) / d);
                                if (fx < -repulsionRange)
                                    fx = -repulsionRange;
                                else if (fx > repulsionRange)
                                    fx = repulsionRange;
                                double fy = Math.Pow(repellingForceConst / d, 2) * ((v.Position.Y - v2.Position.Y) / d);
                                if (fy < -repulsionRange)
                                    fy = -repulsionRange;
                                else if (fy > repulsionRange)
                                    fy = repulsionRange;

                                newForce += new Vector(fx, fy);
                            }
                        }
                    }
                    if (newForce.X > 0 || newForce.Y > 0)
                    {
                        numOfMoves++;
                    }

                    double dampingFactor = ((double)this._MaxIteration - iteration) / this._MaxIteration;
                    //v.Force = newForce * dampingFactor;
                    //v.Velocity += v.Force / 5;
                    //Vector deltaPos = v.Velocity * 0.1;
                    Point forceDrag = newForce.ToPoint();
                    if (forceDrag.X > repulsionRange / 2)
                        forceDrag.X = repulsionRange / 2;
                    if (forceDrag.Y > repulsionRange / 2)
                        forceDrag.Y = repulsionRange / 2;
                    double rx = 10;
                    double ry = 10;
                    if (v.Control != null)
                    {
                        rx = v.Control.Width / 2;
                        ry = v.Control.Height / 2;
                    }
                    double x = v.Position.X + forceDrag.X;
                    double y = v.Position.Y + forceDrag.Y;
                    if (x < constraintRect.Left + rx)
                        x = constraintRect.Left + rx;
                    if (x > constraintRect.Right - rx)
                        x = constraintRect.Right - rx;
                    if (y < constraintRect.Top + ry)
                        y = constraintRect.Top + ry;
                    if (y > constraintRect.Bottom - ry)
                        y = constraintRect.Bottom - ry;
                    v.Position = new Vector(x, y);
                }
            }
            

            if(numOfMoves==0)
            {
                this.IsStopped = true;
            }
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="dc"></param>
		/// <param name="netVisual"></param>
		/// <param name="vertices"></param>
		/// <param name="edges"></param>
		public void RenderEdges(DrawingContext dc, NetworkVisual netVisual,
            List<Vertice> vertices, List<Edge> edges)
		{
			Dictionary<Guid, Vertice> vs = new Dictionary<Guid, Vertice>();
			foreach (Vertice v in vertices)
			{
				vs.Add(v.ID, v);
			}
			foreach (Edge e in edges)
			{
				Vertice v1 = vs[e.VerticeID1];
				Vertice v2 = vs[e.VerticeID2];
			    EdgeVisual ev = null;
                if(netVisual.EdgeVisuals.ContainsKey(v1.Tag.EntityType) && 
                    netVisual.EdgeVisuals[v1.Tag.EntityType].ContainsKey(v2.Tag.EntityType))
                {
                    ev = netVisual.EdgeVisuals[v1.Tag.EntityType][v2.Tag.EntityType];
                }
				Point p1 = v1.Position.ToPoint();
				Point p2 = v2.Position.ToPoint();
                if(v2.Control !=null)
                {
                    double d = (v1.Position - v2.Position).Length;
                    double dx = Math.Abs(p1.X - p2.X)/d*v2.Control.Width/2;
                    double dy = Math.Abs(p1.Y - p2.Y)/d*v2.Control.Height/2;
                    if(p1.X < p2.X)
                        p2.X -= dx;
                    else
                        p2.X += dx;
                    if(p1.Y < p2.Y)
                        p2.Y -= dy;
                    else
                        p2.Y += dy;
                }

			    Color edgeColor = e.PenColor;
				Pen pen = new Pen(new SolidColorBrush(e.PenColor), e.Thickness);
				if (e.LineStyle == EdgeLineStyle.Dashed)
					pen.DashStyle = DashStyles.Dash;
                if(ev !=null)
                {
                    edgeColor = ev.Color;
                    pen.Brush = new SolidColorBrush(ev.Color);
                    if (ev.ThicknessMeasure == EdgeThicknessMeasure.Fixed)
                        pen.Thickness = ev.Thickness;
                    else if(ev.ThicknessMeasure==EdgeThicknessMeasure.Distance)
                    {
                        pen.Thickness = Math.Max(1, 10 - e.Tag.Distance);
                    }
                    else
                    {
                        pen.Thickness = e.Tag.Aggregates;
                    }
                    if(ev.LineStyle==EdgeLineStyle.Dashed)
                        pen.DashStyle = DashStyles.Dash;
                }
                if (v1.Disabled || v2.Disabled)
                    pen.Brush = new SolidColorBrush(Color.FromArgb(10, edgeColor.R, edgeColor.G, edgeColor.B));
                if (e is Arc)
                {
                    this.DrawArrow(dc, pen, p1, p2);
                    //pen.EndLineCap = PenLineCap.Triangle;
                    //dc.DrawLine(pen, p1, p2);
                }
                else
                {
                    pen.EndLineCap = PenLineCap.Round;
                    dc.DrawLine(pen, p1, p2);
                }
			}
		}

		private void DrawArrow(DrawingContext dc, Pen pen, Point p1, Point p2)
		{
			Point[] aptArrowHead = new Point[3];
			aptArrowHead[0] = p2;
			DrawingVector vecLine = new DrawingVector(p2.X - p1.X, p2.Y - p1.Y);// build the line vector
			DrawingVector vecLeft = new DrawingVector(-vecLine[1], vecLine[0]);// build the arrow base vector - normal to the line
			double arrowWidth = 8f;
			double arrowTheta = 0.5f;
			// setup remaining arrow head points
			double lineLength = vecLine.Length;
			double th = arrowWidth / (2.0f * lineLength);
			double ta = arrowWidth / (2.0f * ((double)Math.Tan(arrowTheta / 2.0f)) * lineLength);

			// find the base of the arrow
			Point pBase = new Point(aptArrowHead[0].X + -ta * vecLine[0], aptArrowHead[0].Y + -ta * vecLine[1]); //base of the arrow

			// build the points on the sides of the arrow
			aptArrowHead[1] = new Point(pBase.X + th * vecLeft[0], pBase.Y + th * vecLeft[1]);
			aptArrowHead[2] = new Point(pBase.X + -th * vecLeft[0], pBase.Y + -th * vecLeft[1]);

			dc.DrawLine(pen, p1, pBase); //master line

			dc.DrawLine(pen, aptArrowHead[0], aptArrowHead[1]);
			dc.DrawLine(pen, aptArrowHead[1], aptArrowHead[2]);
			dc.DrawLine(pen, aptArrowHead[2], aptArrowHead[0]);
		}
		#endregion
	}
}
